'''
Created on 2011-12-02

@author: stuart
'''
from PyQt4.QtCore import *
from PyQt4.QtGui import *

import numpy
import transform
import math

FOV_Y_DEFAULT = 0.69813170079772 # 40 degrees in radians
Z_NEAR_DEFAULT = 2.0
Z_FAR_DEFAULT = 400.0
ASPECT_DEFAULT = 640.0 / 360.0 # output dimensions

INIT_POS = numpy.array([90, 95, 85])
INIT_PITCH = math.pi/12.0
INIT_HEADING = -math.pi/8.0

class InteractiveCamera(object):

    # define some constants for different mouse mappings
    rotate = 1
    pan = 2
    dolly = 4

    def __init__(self, fovy=FOV_Y_DEFAULT, aspect=ASPECT_DEFAULT, zNear=Z_NEAR_DEFAULT, zFar=Z_FAR_DEFAULT):
        super(InteractiveCamera, self).__init__()
        self.fovy = fovy
        self.aspect = aspect
        self.zNear = zNear
        self.zFar = zFar

        self.position = INIT_POS
        self.heading = INIT_HEADING
        self.pitch = INIT_PITCH
        self.p = transform.perspective(self.fovy, self.aspect,
                                       self.zNear, self.zFar)
        
        self.xPan = 0.0
        self.yPan = 0.0
        self.zDolly = 0.0
        self._updateModelView()    
        
        self.mousePressPos = QPointF()          

    def _updateModelView(self):
        t1 = transform.translate([-self.position[0],
                                  -self.position[1],
                                  -self.position[2]])
        t2 = transform.rotate_y(self.heading)
        t3 = transform.rotate_x(self.pitch)    
        t4 = transform.translate([self.xPan, self.yPan, self.zDolly])    

        self.mv = transform.compose(t4, t3, t2, t1)

    def modelView(self):
        return self.mv

    def projection(self):
        return self.p
    
    def getPitch(self):
        return self.pitch
    
    def getHeading(self):
        return self.heading

    def getPosition(self):
        return self.position

    def setPosition(self, newPosition):
        self.position = newPosition
        self._updateModelView()
        
    def getAspect(self):
        return self.aspect
        
    def mousePressEvent(self, event, w, h):
        self.mousePressPos = event.posF()
        mods = event.modifiers()
        if not bool(mods):
            self.dragMode = InteractiveCamera.rotate
            self.oldHeading = self.heading
            self.oldPitch = self.pitch
            self.mappingScale = math.pi/float(h)
        elif mods == Qt.ShiftModifier:
            self.dragMode = InteractiveCamera.pan
            self.oldPosition = self.position
            self.mappingScale = 1.0/float(h)
        elif mods == Qt.ControlModifier:
            self.dragMode = InteractiveCamera.dolly
            self.oldPosition = self.position
            self.mappingScale = 1.0/float(h) # appears in an exponent...
        else:
            self.dragMode = 0
        return False # nothing about the camera has changed yet...

    def mouseMoveEvent(self, event):
        diff = event.posF() - self.mousePressPos
        xDiff = diff.x()
        yDiff = diff.y()
        if self.dragMode == InteractiveCamera.rotate:
            self.heading = math.fmod(self.mappingScale*xDiff + self.oldHeading, 2*math.pi)
            self.pitch = min(math.pi/2, max(-math.pi/2, self.mappingScale*yDiff + self.oldPitch))
            self._updateModelView()
            return True
        
        elif self.dragMode == InteractiveCamera.pan:
            # xDiff corresponds to translating along the camera space x axis
            xTranslate = xDiff * self.mappingScale
            self.xPan -= xTranslate
            
            # yDiff corresponds to translating along the camera space y axis
            yTranslate = yDiff * self.mappingScale
            self.yPan -= yTranslate
            
            self._updateModelView()
            return True
        
        elif self.dragMode == InteractiveCamera.dolly:
            # yDiff corresponds to translating along the camera space z axis
            zTranslate = yDiff * self.mappingScale
            self.zDolly += zTranslate
            
            self._updateModelView()
            return True
        
        else:
            return False # no need to trigger a re-render

    def mouseReleaseEvent(self, event):
        self.dragMode = 0
        return False # nothing about the camera has changed here

